package com.example.network.service.impl;

import com.example.network.controller.dto.ShareDTO;
import com.example.network.controller.request.FileRequest;
import com.example.network.exception.ServiceException;
import com.example.network.mapper.FileMapper;
import com.example.network.model.entity.FileRecord;
import com.example.network.service.FileService;
import com.example.network.service.TimeService;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author LR
 */
@Slf4j
@Service("fileService")
public class FileServiceImpl implements FileService {

    @Resource
    private FileMapper fileMapper;
    @Resource
    private TimeService timeService;
    private static final String REMOTE_DIRECTORY = "/vdb1/www/NetWorkDisk/com/Disk/";  // 远程服务器上的目标目录

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    /**
     * 从本地上传文件到服务器
     *      预防用户上传大文件，采用流式分块上传
     * @return
     */

    @SneakyThrows
    public String uploadFile(MultipartFile chunk, String fileName, int chunkNumber, int totalChunks, String userID,String chunkIndex) {

        FileRecord fileRecord = fileMapper.selectByFileName(fileName, userID);
        if (fileRecord != null) {
            throw new ServiceException("该文件已存在");
        }

        Path chunkPath = null;
        // 初始化SHA-256哈希算法实例
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        // 用于构建哈希值的字符串表示（这里主要用于调试或日志记录）
        StringBuilder hashBuilder = new StringBuilder();

        try {
            // 创建临时目录
            Path chunkDirectory = Paths.get(REMOTE_DIRECTORY + userID + "/chunks/");
            Files.createDirectories(chunkDirectory);
            // 根据文件名和分块编号，生成当前分块的存储路径
            chunkPath = chunkDirectory.resolve(fileName + ".part" + chunkNumber);

            if (Files.exists(chunkPath)) {
                return "分块已存在，请等待合并...";
            }

            // 使用try-with-resources语句打开文件分块的输入流，并缓冲
            try (InputStream inputStream = new BufferedInputStream(chunk.getInputStream());
                 // 打开文件输出流，用于将文件分块写入磁盘
                 FileOutputStream outputStream = new FileOutputStream(chunkPath.toFile())) {
                //定义一个4KB大小的缓冲区
                byte[] buffer = new byte[4096];
                // 存储每次从输入流中读取的字节数
                int bytesRead;
                // 循环读取输入流中的数据
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    // 将读取的数据写入输出流
                    outputStream.write(buffer, 0, bytesRead);
                }
            }

            // 使用chunkIndex更新哈希值（这里实际上是对chunkIndex的哈希，而不是文件内容的哈希）
            digest.update(chunkIndex.getBytes(StandardCharsets.UTF_8));
            // 将chunkIndex追加到hashBuilder中（主要用于调试或日志记录）
            hashBuilder.append(chunkIndex);

            // 检查是否所有分块都已接收
            if (chunkNumber == totalChunks - 1) {
                // 合成文件
                // 定义最终文件的存储目录
                Path finalDirectory = Paths.get(REMOTE_DIRECTORY + userID);
                Files.createDirectories(finalDirectory);
                // 文件的路径
                Path finalPath = finalDirectory.resolve(fileName);
                // 打开最终文件的输出流，并缓冲
                try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(finalPath.toFile()))) {
                    // 获取当前分块的路径
                    for (int i = 0; i < totalChunks; i++) {
                        Path chunkFile = chunkDirectory.resolve(fileName + ".part" + i);
                        // 打开当前分块的输入流，并缓冲
                        try (InputStream fis = new BufferedInputStream(new FileInputStream(chunkFile.toFile()))) {
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            while ((bytesRead = fis.read(buffer)) != -1) {
                                // 将读取的数据写入最终文件的输出流
                                fos.write(buffer, 0, bytesRead);
                            }
                        }
                        // 删除当前临时分块文件，因为已经合并到最终文件中
                        Files.delete(chunkFile);
                    }
                }


                // 计算并获取最终文件的哈希值
                byte[] hashBytes = digest.digest();
                String fileChunkIndexHash = bytesToHex(hashBytes);

                // 设置文件属性并写入数据库
                long fileSize = Files.size(finalPath);
                Boolean flag = fileMapper.insert(new FileRecord(fileName,
                        String.valueOf(finalPath),
                        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm")),
                        userID,
                        fileSize,
                        fileChunkIndexHash));
                if (!flag) {
                    throw new RuntimeException("数据库保存错误");
                }

                timeService.insertTime(userID, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm")),"上传文件");

                // 清理临时目录（如果为空）
                try (Stream<Path> paths = Files.list(chunkDirectory)) {
                    List<Path> files = (List<Path>) paths.filter(Files::isRegularFile).collect(Collectors.toList());
                    if (files.isEmpty()) {
                        Files.delete(chunkDirectory);
                    }
                }

                return "文件上传成功！唯一标识："+fileChunkIndexHash;
            }

            return "分块上传成功，唯一标识："+chunkIndex+"分块编号："+chunkNumber;
        } catch (IOException e) {
            Files.delete(chunkPath);
//            e.printStackTrace();
            return "文件上传失败！中断时正在上传的分块编号：" + chunkNumber;
        }
    }

    /**
     * 删除文件
     *
     * @param filename
     * @param userID
     * @return
     */
    @SneakyThrows
    @Override
    public Boolean deleteByFileName(String filename, String userID) {
        File file = new File(REMOTE_DIRECTORY+userID+"/"+ filename);
        if (!file.exists()){
            throw new ServiceException("文件不存在！");
        }
        boolean deleted = file.delete();
        if (deleted) {
            fileMapper.deleteByFileName(filename,userID);
            return true;
        }
        return false;
    }

    /**
     * 下载文件
     */
    @SneakyThrows
    @Override
    public ResponseEntity<FileSystemResource> downloadFile(String filename, String userID) throws IOException {
        FileRequest request = fileMapper.getFile(filename,userID);
        if(request==null){
            throw new ServiceException("文件不存在！");
        }
        // 文件路径
        Path path = Paths.get(REMOTE_DIRECTORY+request.getUserID()+"/"+ request.getFilename());

        // 通过路径获取文件资源
        FileSystemResource resource = new FileSystemResource(path.toFile());

        String filename1 = resource.getFilename();
        String encodedFilename = URLEncoder.encode(filename1, "UTF-8").replace("+", "%20");
        String contentDispositionValue = "attachment; filename*=UTF-8''" + encodedFilename;

        // 返回一个包含文件内容的HTTP响应
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM) // 设置内容类型为二进制流
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDispositionValue) // 设置Content-Disposition头，以附件形式下载，并指定下载后的文件名
                .body(resource); // 设置响应体为文件资源
    }

    /**
     * 查看所有文件地址
     * @return
     */
    @Override
    public List<FileRequest> selectByAll(String userID) {
        return fileMapper.selectByAll(userID);
    }

    /**
     * 创建分享链接
     *
     * @param filename
     * @return
     */
    @Override
    public ShareDTO shareFile(String filename,String userID){
        ShareDTO shareDTO = fileMapper.findByFilePath(filename,userID);
        if (shareDTO.getShareTime()<System.currentTimeMillis()){
            shareDTO.setShareID(UUID.randomUUID().toString());
            shareDTO.setFilePass(RandomStringUtils.randomAlphabetic(4));// 生成4位随机字母作为密码
            shareDTO.setShareTime(System.currentTimeMillis()+24*60*60*1000);
            fileMapper.updateShareID(shareDTO);
        }
        return shareDTO;
    }

    @Override
    public ShareDTO findShareID(String shareID,String filePass) {
        ShareDTO shareDTO = fileMapper.findShareID(shareID);
        if (shareDTO==null){
            throw new ServiceException("文件不存在或已被上传者删除");
        }
        if (!shareDTO.getFilePass().equals(filePass)){
            throw new ServiceException("提取码有误");
        }
        if (shareDTO.getShareTime()<System.currentTimeMillis()){
            throw new ServiceException("该分享链接已过期");
        }
        return shareDTO;
    }

    /**
     * 通过分享链接下载文件
     *
     * @param filePath
     * @return
     */
    @SneakyThrows
    @Override
    public ResponseEntity<FileSystemResource> downloadFileByShareID(String filePath) {
        // 文件路径
        Path path = Paths.get(filePath);

        // 通过路径获取资源
        FileSystemResource resource = new FileSystemResource(path.toFile());
        if (!resource.exists()) {
            return ResponseEntity.notFound().build();
        }

        String filename1 = resource.getFilename();
        String encodedFilename = URLEncoder.encode(filename1, "UTF-8").replace("+", "%20");
        String contentDispositionValue = "attachment; filename*=UTF-8''" + encodedFilename;

        // 返回一个包含文件内容的HTTP响应
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM) // 设置内容类型为二进制流
                .header(HttpHeaders.CONTENT_DISPOSITION, contentDispositionValue) // 设置Content-Disposition头，以附件形式下载，并指定下载后的文件名
                .body(resource); // 设置响应体为文件资源
    }


    /**
     * 按文件名搜索远程文件地址
     * 可不输入全名
     *
     * @param filename
     * @param userID
     * @return
     */
    @Override
    public List<FileRequest> selectByFileName(String filename, String userID){
        return fileMapper.findByFilenamePrefix(filename,userID);
    }

}


